#!/usr/bin/env node
'use strict';
const fs = require('fs-extra');
const axios = require('axios');
const chalk = require('chalk');
const nodePath = require('path');
const signale = require('signale');
const { prompt } = require('prompts');
const commandLineArgs = require('command-line-args');
const commandLineUsage = require('command-line-usage');

// import components
const log = require('./src/components/logger');
const downloadState = require('./src/components/downloadState');
const { ensureDir } = require('./src/components/utils');

// import service
const defaults = require('./src/defaults');
const musicQualities = require('./src/musicQualities');
const downloadTypes = require('./src/downloadTypes');
const downloadMultiple = require('./src/downloadMultiple');

// check and notify about update
const updateNotifier = require('./src/updateCheck');
const pkg = require('./package.json');
updateNotifier(pkg);

// init deezer api
const config = require('./src/config');
const deezerApi = require('deezer-request2');

const isCli = process.argv.length > 2;
const cliOptionDefinitions = [
  {
    name: 'help',
    alias: 'h',
    description: 'Print this usage guide :)',
  },
  {
    name: 'quality',
    alias: 'q',
    type: String,
    description: 'The quality of the files to download: 128/320/FLAC',
  },
  {
    name: 'path',
    alias: 'p',
    type: String,
    description: 'The path to download the files to: path with / in the end',
  },
  {
    name: 'url',
    alias: 'u',
    type: String,
    defaultOption: true,
    description: 'Downloads single deezer url: album/artist/playlist/track url',
  },
  {
    name: 'downloadmode',
    alias: 'd',
    type: String,
    description: 'Downloads multiple urls from list: "all" for downloadLinks.txt',
  },
  {
    name: 'concurrency',
    alias: 'c',
    type: Number,
    description: 'Download concurrency for album, artists and playlist',
  },
  {
    name: 'no-progress',
    alias: 'n',
    type: Boolean,
    description: 'Hide download progress bar',
  },
  {
    name: 'set-arl',
    alias: 'a',
    type: String,
    description: 'Set arl cookie',
  },
];

const onCancel = (prompt) => {
  console.log('Abort!');
  process.exit();
};

global.bar = true;

/**
 * Application init.
 */
const initApp = async () => {
  // App info
  console.log(chalk.cyan('╔══════════════════════════════════════════════════════════╗'));
  console.log(
    chalk.cyan('║') +
      chalk.bold.yellow(`                    d-fi (${pkg.version})                          `) +
      chalk.cyan('║')
  );
  console.log(chalk.cyan('╠══════════════════════════════════════════════════════════╣'));
  console.log(
    chalk.redBright(' ♥  REPO   ') +
      chalk.cyan('║') +
      ' https://notabug.org/sayem314/d-fi             ' +
      chalk.cyan('║')
  );
  console.log(chalk.cyan('╠══════════════════════════════════════════════════════════╣'));
  console.log(
    chalk.redBright(' ♥  DONATE ') +
      chalk.cyan('║') +
      ' https://sayem.eu.org/donate                   ' +
      chalk.cyan('║')
  );
  console.log(chalk.cyan('╚══════════════════════════════════════════════════════════╝\n'));
  // console.log(chalk.yellow('Please read the latest manual thoroughly before asking for help!\n'));

  if (isCli) {
    try {
      let cliOptions = commandLineArgs(cliOptionDefinitions);

      for (let [key, value] of Object.entries(cliOptions)) {
        switch (key) {
          case 'url':
            defaults.DOWNLOAD_URL = value;
            break;
          case 'quality':
            defaults.DOWNLOAD_QUALITY = value;
            break;
          case 'path':
            defaults.DOWNLOAD_DIR = value;
            break;
          case 'downloadmode':
            defaults.DOWNLOAD_MODE = value;
            break;
          case 'concurrency':
            defaults.CONCURRENCY = value;
            break;
          case 'no-progress':
            global.bar = false;
            break;
          case 'set-arl':
            if (value) {
              config.set('cookies.arl', value);
              signale.info('arl cookie set to: ' + value);
            }
            process.exit();
            break;
          default:
            const helpSections = [
              {
                header: 'CLI Options',
                optionList: cliOptionDefinitions,
              },
              {
                content: 'More info here: https://notabug.org/sayem314/d-fi',
              },
            ];

            console.log(commandLineUsage(helpSections));
            process.exit();
        }
      }
    } catch (err) {
      log.debug(err.message || err);
      signale.fatal(err.message || err);
      process.exit(1);
    }
  }

  try {
    // init api
    signale.info('Initializing session..');
    await deezerApi.initDeezerApi(config.get('cookies.arl'));

    defaults.DOWNLOAD_DIR = nodePath.normalize(nodePath.resolve(defaults.DOWNLOAD_DIR.replace(/\/$|\\$/, '')));
    selectMusicQuality();
  } catch (err) {
    log.debug(err);
    process.exit(1);
  }
};

/**
 * Show user selection for the music download quality.
 */
const selectMusicQuality = async () => {
  if (isCli) {
    switch (defaults.DOWNLOAD_QUALITY) {
      case '128':
      case 'MP3_128':
        musicQualities.selectedQuality = musicQualities.qualities.MP3_128;
        break;
      case '320':
      case 'MP3_320':
        musicQualities.selectedQuality = musicQualities.qualities.MP3_320;
        break;
      case 'flac':
      case 'Flac':
      case 'FLAC':
        musicQualities.selectedQuality = musicQualities.qualities.FLAC;
        break;
      default:
        musicQualities.selectedQuality = musicQualities.qualities.MP3_320;
    }

    if (defaults.DOWNLOAD_MODE == 'all') {
      downloadLinksFromFile();
    } else {
      try {
        await startDownload(defaults.DOWNLOAD_URL);
        process.exit();
      } catch (err) {
        log.debug(err.message);
        signale.fatal(err);
        downloadState.finish();
        process.exit(1);
      }
    }
  } else {
    let answers = await prompt(
      [
        {
          type: 'select',
          name: 'musicQuality',
          message: 'Select music quality:',
          choices: [{ title: 'MP3  - 128  kbps' }, { title: 'MP3  - 320  kbps' }, { title: 'FLAC - 1411 kbps' }],
          initial: 1,
        },
      ],
      {
        onCancel,
      }
    );

    switch (answers.musicQuality) {
      case 0:
        musicQualities.selectedQuality = musicQualities.qualities.MP3_128;
        break;
      case 2:
        musicQualities.selectedQuality = musicQualities.qualities.FLAC;
        break;
      default:
        musicQualities.selectedQuality = musicQualities.qualities.MP3_320;
    }

    log.debug('Selected music quality: ' + answers.musicQuality);

    selectDownloadMode();
  }
};

/**
 * Ask for download mode (single or all).
 */
const selectDownloadMode = async () => {
  let answers = await prompt(
    [
      {
        type: 'select',
        name: 'downloadMode',
        message: 'Select download mode:',
        choices: [
          { title: 'Single (Download single link)' },
          { title: 'All    (Download all links in "' + defaults.DOWNLOAD_LINKS_FILE + '")' },
        ],
        initial: 0,
      },
    ],
    {
      onCancel,
    }
  );
  if (answers.downloadMode === 1) {
    downloadLinksFromFile();
  } else {
    askForNewDownload();
  }
};

/**
 * Download all links from file
 */
const downloadLinksFromFile = async () => {
  if (!fs.existsSync(defaults.DOWNLOAD_LINKS_FILE)) {
    ensureDir(defaults.DOWNLOAD_LINKS_FILE);
    fs.writeFileSync(defaults.DOWNLOAD_LINKS_FILE, '');
  }
  const lines = fs
    .readFileSync(defaults.DOWNLOAD_LINKS_FILE, 'utf-8')
    .split(/^(.*)[\r|\n]/)
    .filter(Boolean);

  if (lines[0]) {
    const firstLine = lines[0].trim();
    if ('' === firstLine) {
      removeFirstLineFromFile(defaults.DOWNLOAD_LINKS_FILE);
      await downloadLinksFromFile();
    } else {
      try {
        await startDownload(firstLine, defaults.DOWNLOAD_DIR, musicQualities.selectedQuality.id, true);
        removeFirstLineFromFile(defaults.DOWNLOAD_LINKS_FILE);
        await downloadLinksFromFile();
      } catch (err) {
        log.debug(err);
        signale.fatal(err.message || err);
        downloadState.finish(false);
        removeFirstLineFromFile(defaults.DOWNLOAD_LINKS_FILE);
        await downloadLinksFromFile();
      }
    }
  } else {
    signale.success('Finished downloading from text file');
    if (isCli) {
      process.exit();
    } else {
      console.log('\n');
      selectDownloadMode();
    }
  }
};

/**
 * Remove the first line from the given file.
 *
 * @param {String} filePath
 */
const removeFirstLineFromFile = (filePath) => {
  const lines = fs
    .readFileSync(filePath, 'utf-8')
    .split(/^(.*)[\r|\n]/)
    .filter(Boolean);

  let contentToWrite = '';

  if (lines[1]) {
    contentToWrite = lines[1].trim();
  }

  fs.writeFileSync(filePath, contentToWrite);
};

/**
 * Ask for a album, playlist or track link to start the download.
 */
const askForNewDownload = async () => {
  let questions = [
    {
      type: 'text',
      name: 'deezerUrl',
      message: 'Query:',
    },
  ];

  let answers = await prompt(questions, { onCancel });
  if (!answers.deezerUrl) {
    process.exit();
  }

  try {
    await startDownload(answers.deezerUrl);
    askForNewDownload();
  } catch (err) {
    log.debug(err.message || err);
    signale.fatal(err.message || err);
    downloadState.finish();
    askForNewDownload();
  }
};

/**
 * Start a deezer download.
 *
 * @param {String}  deezerUrl
 * @param {Boolean} downloadFromFile
 */
const startDownload = async (
  deezerUrl,
  path = defaults.DOWNLOAD_DIR,
  quality = musicQualities.selectedQuality.id,
  downloadFromFile = false
) => {
  log.debug('Started download task: ' + deezerUrl);

  try {
    let downloadType = await downloadTypes(deezerUrl);

    if (downloadType.type == 'unknown') {
      const { data } = await axios.get('https://api.deezer.com/search?q=' + encodeURIComponent(deezerUrl));
      const answer = await prompt(
        [
          {
            type: 'select',
            name: 'url',
            message: 'Select a song:',
            choices: data.data.map((item) => {
              item.description = `${item.artist.name} - ${item.link} - ${item.duration}s`;
              item.value = item.link;
              return item;
            }),
            initial: 0,
          },
        ],
        {
          onCancel,
        }
      );
      downloadType = await downloadTypes(answer.url);
    }

    downloadState.start(downloadType.type, downloadType.id);
    const value = await downloadMultiple(downloadType, path, quality);
    downloadState.finish(!downloadFromFile);
  } catch (err) {
    log.debug(err);
    signale.fatal(err.message || err);
  }
};

initApp();
